using Buildin.Components;
using Core;
using Registry;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;

namespace Ecs.System
{
    public class RenderSystem : SystemBase
    {
        private Material _material;
        private Mesh _mesh;
        
        // 批次渲染常量(GPU Instance最大限制)
        private const int MaxInstancesPerBatch = 1023;
        
        // Shader交互ID
        // private static readonly int TransformMatrixDataID = Shader.PropertyToID("_TransformMatrixData");

        public RenderSystem(EcsWorld world, params EArchetypeRegistryID[] registry) : base(world, registry)
        {
            CreateQuadMesh();
            _material = Resources.Load<Material>("RendererTest");
        }
        
        public override void Dispose()
        {
        }

        public override void OnTick(float dt)
        {
            Profiler.BeginSample("RenderSystem");
            
            // 收集渲染所需数据
            foreach (var aid in ArchetypeIDs)
            {
                var archetype = World.GetArchetype(aid);
                var entityCount = archetype.EntityCount;
                if (entityCount <= 0) continue;
                
                using var areaCompsSlice = World.GetEntityComponentDataMemorySlice<Area2DComponent>(aid);
                using var transformsSlice = World.GetEntityComponentDataMemorySlice<TransformComponent>(aid);
                using var validInstanceInfos = new NativeList<RenderInstanceInfo>(entityCount, Allocator.TempJob);
                // 收集渲染矩阵数据                
                new CollectRenderDataJob()
                {
                    Archetype = archetype,
                    AreaComps = areaCompsSlice,
                    TransformComps = transformsSlice,
                    InstanceInfosWriter = validInstanceInfos.AsParallelWriter(),
                }.Schedule(entityCount, 0).Complete();

                // 根据收集数据渲染
                for (var i = 0; i < validInstanceInfos.Length; i += MaxInstancesPerBatch)
                {
                    var batchSize = math.min(MaxInstancesPerBatch, validInstanceInfos.Length - i);
                    BatchRender(validInstanceInfos, i, batchSize);
                }
            }
            Profiler.EndSample();
        }
        
        private void CreateQuadMesh()
        {
            _mesh = new Mesh();
            _mesh.vertices = new Vector3[]
            {
                new Vector3(-0.5f, -0.5f, 0),
                new Vector3(0.5f, -0.5f, 0),
                new Vector3(0.5f, 0.5f, 0),
                new Vector3(-0.5f, 0.5f, 0)
            };
            _mesh.triangles = new int[] { 0, 1, 2, 0, 2, 3 };
            _mesh.uv = new Vector2[]
            {
                new Vector2(0, 0),
                new Vector2(1, 0),
                new Vector2(1, 1),
                new Vector2(0, 1)
            };
            _mesh.RecalculateNormals();
        }

        private void BatchRender(NativeList<RenderInstanceInfo> infos, int startIndex, int batchSize)
        {
            // 计算包围盒
            Profiler.BeginSample("BatchRender准备数据");
            using var matrices = new NativeArray<Matrix4x4>(batchSize, Allocator.TempJob);
            using var positions = new NativeArray<Vector3>(batchSize, Allocator.TempJob);
            new PrepareBatchDataJob()
            {
                InstanceInfos = infos,
                Matrices = matrices,
                Positions = positions,
                StartIndex = startIndex,
            }.Schedule(batchSize, 0).Complete();
            Vector3 minPoints = positions[0], maxPoints = positions[0];
            for (var i = 0; i < batchSize; i++)
            {
                minPoints = math.min(minPoints, positions[i]);
                maxPoints = math.max(maxPoints, positions[i]);
            }
            
            var center = minPoints + maxPoints * 0.5f;
            var size = (maxPoints - minPoints) + Vector3.one * 5f;
            // 确保包围盒不会太小
            size = Vector3.Max(size, Vector3.one * 2f);
            var bounds = new Bounds(center, size * 100);
            Profiler.EndSample();
            
            // 创建MaterialPropertyBlock
            var propertyBlock = new MaterialPropertyBlock();
            // propertyBlock.SetMatrixArray(TransformMatrixDataID, matrices.ToArray());
            var renderParams = new RenderParams()
            {
                material = _material,
                matProps = propertyBlock,
                worldBounds = bounds,
            };
            Graphics.RenderMeshInstanced(renderParams, _mesh, 0, matrices, batchSize);
        }

        private struct RenderInstanceInfo
        {
            public Matrix4x4 Matrix;
        }
        
        [BurstCompile]
        private struct CollectRenderDataJob : IJobParallelFor
        {
            [ReadOnly] public Archetype Archetype;
            [ReadOnly] public ComponentDataMemorySlice<Area2DComponent> AreaComps;
            public ComponentDataMemorySlice<TransformComponent> TransformComps;
            public NativeList<RenderInstanceInfo>.ParallelWriter InstanceInfosWriter;

            public void Execute(int eid)
            {
                if (Archetype.EntityStatuses[eid] == EntityStatusType.Disabled) return;
                var areaComp = AreaComps[eid];
                var transformComp = TransformComps[eid];
                transformComp.Position = new float3(areaComp.Center.x, 0, areaComp.Center.y);
                InstanceInfosWriter.AddNoResize(new RenderInstanceInfo()
                {
                    Matrix = float4x4.TRS(
                        transformComp.Position,
                        Quaternion.Euler(transformComp.Rotation),
                        transformComp.Scale),
                });

                TransformComps[eid] = transformComp;
            }
        }
        
        [BurstCompile]
        private struct PrepareBatchDataJob : IJobParallelFor
        {
            [ReadOnly] public NativeArray<RenderInstanceInfo> InstanceInfos;
            public NativeArray<Matrix4x4> Matrices;
            public NativeArray<Vector3> Positions;
            public int StartIndex;
            public void Execute(int index)
            {
                var data = InstanceInfos[StartIndex + index];
                Matrices[index] = data.Matrix;
                Positions[index] = data.Matrix.GetPosition();
            }
        }
    }
}
